Repository navigation
snap census: self-classifying env/argv read sites, walker-lock unwind funnel, pending-job rows (instrumented builds only) - #14449
Merged
Merged
Conversation
added 28 commits
November 6, 2026 18:20
… (SNAP25) Instrumented builds only (cfg(perry_gc_instruments)); the production build is unchanged. PERRY_SNAP25=0 restores the SNAP23 rows. - Native rows carry the captured return pcs. - Funnels (exec_path, stack_maps, stack_bounds, parse, parallelism, gc, exec_argv) tag the syscalls and native rows issued inside them; the GC heap budget's first computation is in the gc funnel. - navigator.hardwareConcurrency is an input call; env reads inside the exec_argv funnel are program inputs (guard members, never knobs). - unsetenv/setenv rows record whether the name was set; timer, uptime, console.time and GC-stats clocks are input rows. - Every Error stack capture records whether its pcs lie in the executable text. - Census compiles mark each process.argv read: length, constant index (Number or Integer literal) or escape. - The fault handler brings a tracked page whose policy is open but whose protection is still an earlier bulk protect back to its policy instead of treating the fault as foreign.
# Conflicts: # crates/perry-runtime/src/process/env_misc.rs
…P-reach rows; fixtures F1 F2 F3 F10 (instr-only)
…y words, owned storage in the (c) scan (instr-only) - F1/F2/F3/F10 call gc() before the output, so the boundary report and the K2a rows are written; checkk2.py fails "no boundary row" first. - on_trap applies protection after every store of the step is recorded; note_access returns whether the first-write watch ended and on_segv calls end_watch(). A nested fault inside on_trap reports 0xdead instead of dying on the blocked SIGTRAP. - P-entry (a) uses its own word set `ew` (last writer is an entry); (a) is a conflict rule and is read from the PERRY_SNAP22_WATCH=0 run. - The p_reach row follows the init_enter event (exact Ir accounting join). - (c) scan: an entry birth reached through an owned-storage edge of a non-entry holder (ObjectHeader.meta; ObjectMeta spill, overflow, elements, expando, owned_state) is the holder's representation, not an entry value: no target, scanned as the holder's words. F2 named the global's meta record (born by the runtime, holding the spill that the entry's globalThis.k grew) as a (c) holder; checkk2 F2 now allows only the box. - held rows name holder_type and holder_seq.
# Conflicts: # crates/perry-runtime/src/navigator.rs
…e and job-queue rows, runtime-pc STOP, worker-flag STOP
…lours, TERM=dumb does not) util.styleText with the default stream check now follows node's shouldColorize(process.stdout): with FORCE_COLOR set the text is styled when getColorDepth() > 2, whatever stdout is; otherwise stdout must be a tty and getColorDepth() > 2. The old check returned false for FORCE_COLOR="" (node colours: depth 16) and coloured a tty with TERM=dumb or no TERM (node: depth 2). The decision is tty::stdout_style_bit(), over the same colour-depth lookup as process.stdout.getColorDepth(). tests/parity/run_style_text_color_tests.sh compares stdout with node for FORCE_COLOR unset/""/0/1/2/3/false, NO_COLOR, NODE_DISABLE_COLORS, TERM=dumb, TERM=xterm-256color, no TERM and two FORCE_COLOR combinations, each under a pty and through a pipe.
tty.rs: stdout_style_bit is the style fix plus the instrumented StyleBit funnel and stdout_style_bit row (cfg perry_gc_instruments).
…g text
scripts/snap_inputs/checkk2b.py and fixtures/snapk2b/{f3n,f4,f5,f6,f7,f8,
f12,f13,fw,f2c}: each fixture is red with its rule knob off and green with
it on (f8 confirms its row only).
compare_literal read a heap string through the K2a value encoding
("string:<hex>") but a short string as its text, so equal text in the two
representations compared unequal and the row's literal held the encoding.
string_text now returns the text for both.
f12 queues a promise job at module scope that is still pending when the
next module starts (node order: n sees m unset); a TLA module does not
reach that state in perry, which runs it to completion before its
sibling. f13 makes its Error in a recursive function at module scope: a
module init body is not JS text, so an Error made there captures no frame;
the checker now requires a non-empty capture.
The compare_literal census hook kept a thread-local list of compare sites keyed by Expr address. A compare whose numeric-literal or typeof operand is lowered without lower_expr never completed its entry; once a codegen thread moved to the next unit, an expression at the freed literal's address emitted js_snap_compare_literal with the earlier function's register (Supabase: precise_roots native-root lowering panic in an unrelated closure). expr::compare::lower now emits the hook from its own re-read operand values, right where it compares them. Fast paths (typeof, numeric literal, chained and typed compares) emit no row: the read stays a value member. No site list remains. The new test compiles a module of fast-path compares, then a module with no compare, and asserts the second emits no hook (33 stray hooks on the old code).
The process.env / process.argv census arms looked their parent up in site lists keyed by node address, so a later module whose nodes reused freed addresses read stale entries. The lists are gone: FnCtx keeps the two innermost expressions being lowered (installed by lower_expr and by a module-global let, restored on every return), and each read classifies itself against its own parent. The scalar-replacement paths of lower_let lower literal operands with the literal installed as their parent. Test env_argv_kinds_never_cross_reads.
…side it does not walk the stack A census native event (clock_gettime from mimalloc's arena free, reached from decode_fde_row inside find_handler) captured its stack through the same eh_walker, whose lock the thread already held: the Supabase census run hung at the ws buffer-util throw (init 272). locked_walker returns a WalkerLock that holds the census Unwind funnel in the instrumented cohort; native_event skips the capture inside it and the row carries funnel=unwind. Production builds are unchanged (the funnel field is instrumented-only).
…ode, queueing init) The K3 Supabase census stops at init 1033 on job_queue (pending=1 at every later boundary), but the row only counted the jobs. Each row now lists the queued jobs in queue order as kind:owner:code: the Task variant, the init that queued the job (the snapshot's snap_init, set at enqueue) and the callback's JsFunctionInfo code address (0 for a reaction without a callback). Read from the task itself; instrumented runtime only.
…sus-k3c # Conflicts: # crates/perry-codegen/src/expr/compare.rs # crates/perry-codegen/src/expr/mod.rs
Pre-merge checks:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces draft #14259.
What
Instrumentation of the startup-snapshot census (instrumented compiles and the instrumented runtime only; production codegen and runtime bytes are unchanged):
process.env/process.argvread sites classify against their own parent expression instead of address-keyed site lists, so a later module can no longer read a stale site kind; this also covers literal operands on the scalar-replacement path oflet. Also in the chain: the compare_literal hook from the compare lowering, K2b fixtures and checker, open-frame and job_queue rows.locked_walkerreturns aWalkerLockthat holds the census Unwind funnel in the instrumented cohort; a native census event raised inside it (clock_gettime from mimalloc's arena free, reached fromdecode_fde_rowinsidefind_handler) no longer re-enters the eh_walker and hangs. The row carriesfunnel=unwind. Test:row_pcs_inside_walker_lock_returns.job_queuerow names each pending job askind:owner:code(Task variant, queueing init, callback code address), which identifies the job pending from init 1033 to the end of Supabase init. Test:pending_jobs_name_kind_callback_and_queueing_init.Merged with origin/main 322604f (two mechanical conflicts, both sides kept:
expr/compare.rshook order,expr/mod.rsmodule list).Tests (merged head)
cargo check --workspace --exclude perry-ui-gtk4: rc=0cargo test -p perry-runtime snap(RUST_TEST_THREADS=1): 67 passed, 0 failed, 1 ignored (includes the two tests above)cargo test -p perry-codegen snap(RUST_TEST_THREADS=1): 4 passed, 0 failed (snap_compare_site_tests, snap_env_site_tests)Startup gate (exact callgrind Ir, 5 runs per arm, setarch -R, controls /root/ctl, same binary path, A/A first; main = 322604f)
A/A all zero. File-size deltas outside .text (bytes, not gated): hello +8, commander_help -640, commander -640, fastify_inject -744, awfy_richards +8, binary_trees +8 (link-pad CONVERGED; awfy_richards shows a 64-byte .rodata difference at the end of the main arm's link, no Ir or .text effect).